#pragma once

#include <math.h>
#include <list>
#include <string.h>
using namespace std;
static const float PI = 3.1415926F;
static const float NORMALEN = 0.2F;
static const float DIFFERENCE = 0.00001F;

inline bool isEqual(float x, float y) {
	return (abs(x-y) <= DIFFERENCE);
}

class MVertex{
public:
	float point[4];
	MVertex(float ax = 0, float ay=0, float az=0){
		point[0] = ax;
		point[1] = ay;
		point[2] = az;
		point[3] = 1;
	}

	MVertex(const MVertex &other) {
			point[0] = other.point[0];
			point[1] = other.point[1];
			point[2] = other.point[2];
			point[3] = other.point[3];
	}

	MVertex translate(float dx, float dy, float dz) {
		MVertex rt(*this);
		rt.point[0] += dx;
		rt.point[1] += dy;
		rt.point[2] += dz;
		return rt;
	}

	MVertex rotateX(float theta) {	//rotate around x axis by theta, in radian
		MVertex rt(*this);
		rt.point[1] = cos(theta)*(this->point[1]) - sin(theta)*(this->point[2]);
		rt.point[2] = sin(theta)*(this->point[1]) + cos(theta)*(this->point[2]);
		if(isEqual(rt.point[1],0)) rt.point[1] =0;
		if(isEqual(rt.point[2],0)) rt.point[2] =0;
		return rt;
	}

	MVertex rotateY(float theta) {	//rotate around y axis by theta, in radian
		MVertex rt(*this);
		rt.point[0] = cos(theta)*(this->point[0]) + sin(theta)*(this->point[2]);
		rt.point[2] = -sin(theta)*(this->point[0]) + cos(theta)*(this->point[2]);
		if(isEqual(rt.point[0],0)) rt.point[0] =0;
		if(isEqual(rt.point[2],0)) rt.point[2] =0;
		return rt;
	}

	MVertex rotateZ(float theta) {	//rotate around z axis by theta, in radian
		MVertex rt(*this);
		rt.point[0] = cos(theta)*(this->point[0]) - sin(theta)*(this->point[1]);
		rt.point[1] = sin(theta)*(this->point[0]) + cos(theta)*(this->point[1]);
		if(isEqual(rt.point[1],0)) rt.point[1] =0;
		if(isEqual(rt.point[0],0)) rt.point[0] =0;
		return rt;
	}

	//assuming, sx, sy, sz is not 0
	MVertex scaling(float sx, float sy, float sz) {
		MVertex rt(*this);
		rt.point[0] *= sx;
		rt.point[1] *= sy;
		rt.point[2] *= sz;
		return rt;
	}

	MVertex operator +(const MVertex &o1) {
		MVertex v(*this);
		v.point[0] += o1.point[0];
		v.point[1] += o1.point[1];
		v.point[2] += o1.point[2];
		return v;
	}

	MVertex &operator=(const MVertex &p1) {
		this->point[0] = p1.point[0];
		this->point[1] = p1.point[1];
		this->point[2] = p1.point[2];
		this->point[3] = p1.point[3];
		return *this;
	}

	float length() const{
		return sqrt(pow(point[0],2)+pow(point[1],2)+pow(point[2],2));
	}

	MVertex CrossProduct(const MVertex &p1) {
		MVertex v(*this);
		v.point[0] = this->point[1]*p1.point[2] - this->point[2]*p1.point[1];
		v.point[1] = this->point[2]*p1.point[0] - this->point[0]*p1.point[2];
		v.point[2] = this->point[0]*p1.point[1] - this->point[1]*p1.point[0];
		return v;
	}

	MVertex normailize() {
		float f = 1/length();
		return scaling(f,f,f);
	}

	/*rotate p1 around line from v1 to v2 by theta 
	MVertex rotateAroundLine(float theta, const MVertex &v1, const MVertex &v2, const MVertex &p1) {
		MVertex npos(p1);
		npos = npos.translate(-v1.point[0], -v1.point[1], -v1.point[2]);

	}*/
	MVertex midPoint(const MVertex v1) {
		return MVertex((point[0]+v1.point[0])/2,(point[1]+v1.point[1])/2,(point[2]+v1.point[2])/2);
	}
	MVertex operator *(float x) {
		return scaling(x,x,x);
	}

};


MVertex getVertex(const MVertex &p1, const MVertex &p2);
void calculateNormal(MVertex *p, const MVertex &v, const MVertex &t, 
							const MVertex &b, const MVertex &l, const MVertex &r);

class MMatrix  {
public:
	float data[4][4];
	MMatrix() {
		memset(data,0,sizeof(float)*16);
	}
	//note no error checking for this func 
	void setMatrix(const float &v1, const float &v2, const float &v3, const float &v4, const float &v5, 
		const float &v6, const float &v7, const float &v8, const float &v9, const float &v10,
		const float &v11, const float &v12, const float &v13, const float &v14, const float &v15, 
		const float &v16) {
		data[0][0] = v1; data[0][1] = v2; data[0][2] = v3; data[0][3] = v4;
		data[1][0] = v5; data[1][1] = v6; data[1][2] = v7; data[1][3] = v8;
		data[2][0] = v9; data[2][1] = v10; data[2][2] = v11; data[2][3] = v12;
		data[3][0] = v13; data[3][1] = v14; data[3][2] = v15; data[3][3] = v16;
	}

	MMatrix dotMatrix(const MMatrix &m) {
		MMatrix rt;
		for(int i = 0; i <4; i++) {
			for(int j = 0; j<4; j++) {
				rt.data[i][j] = data[i][0]*m.data[0][j]+data[i][1]*m.data[1][j]
				+ data[i][2]*m.data[2][j] + data[i][3]*m.data[3][j];;
			}
		}
		return rt;
	}

	MVertex dotVertex(const MVertex &v) {
		MVertex nv;
		for(int i=0; i<4; i++) {
			nv.point[i] = data[i][0]*v.point[0] + data[i][1]*v.point[1] + 
				data[i][2]*v.point[2] + data[i][3]*v.point[3];
		}
		return nv;
	}
};

class Shape
{
public:
    string name; 
	bool bNormal, bRotateAroundOwnAxis;
	MVertex v1, v2; //own central axis
	MMatrix matrix;
public:
	Shape(void);
	virtual ~Shape(void);	
	virtual void drawShape() = 0;
	virtual void translate(float x, float y, float z) = 0;
	virtual void rotate(float deg, int x, int y, int z) = 0;
	virtual void rotateAroundOwnAxis(float theta) = 0;
	virtual void scale(float x, float y, float z) = 0;
	virtual void incrementSlice()=0;
	virtual void decrementSlice()=0;
	virtual void incrementStack()=0;
	virtual void decrementStack()=0;
};